#pragma once
#include <zGraphicsConfig.hpp>
#include "Graphics.hpp"
#include "Color.hpp"
#include <Math/Vector2.hpp>
#include "../Context/Context.hpp"
#include <Utility/Thread.hpp>

namespace zzz{
ZGRAPHICS_FUNC bool InitGLEW();
ZGRAPHICS_FUNC void CheckGLVersion();
ZGRAPHICS_FUNC bool CheckGLSL();
ZGRAPHICS_FUNC bool CheckSupport(const string &ext);

ZGRAPHICS_FUNC int CheckGLError(char *file, int line);
#ifndef ENABLE_CHECKGL
#define CHECK_GL_ERROR() ;
#else
#define CHECK_GL_ERROR() zzz::CheckGLError(__FILE__, __LINE__);
#endif

// OpenGL convenience
inline void glTranslatefv(const GLfloat *v) {glTranslatef(v[0],v[1],v[2]);}

inline void glTranslatedv(const GLdouble *v) {glTranslated(v[0],v[1],v[2]);}

inline bool GLExists(){return Context::current_context_!=NULL;}

// Automatically call Restore when destroyed.
template<typename T>
class AutoRestore : public T {
public:
#define CONSTRUCTOR(type) AutoRestore(const type& x) {T::Set(x);}
  CONSTRUCTOR(int);
  CONSTRUCTOR(GLfloat);
  CONSTRUCTOR(GLuint);
  CONSTRUCTOR(Colorf);
  AutoRestore(GLenum face, GLenum mode) {
    T::Set(face, mode);
  }
  ~AutoRestore() {
    T::Restore();
  }
  using T::Set;
  using T::Get;
  using T::Restore;
};

// OpenGL status setters
// How useful they are!
class GLLineWidth {
public:
  static float Set(GLfloat x) {
    old=Get();
    glLineWidth(x);
    return old;
  }
  static void Restore() {Set(old);}
  static float Get() {
    GLfloat size;
    glGetFloatv(GL_LINE_WIDTH,&size);
    return size;
  }
  static GLfloat old;
};

class GLPointSize {
public:
  static float Set(GLfloat x) {
    old=Get();
    glPointSize(x);
    return old;
  }
  static void Restore() {Set(old);}
  static float Get() {
    GLfloat size;
    glGetFloatv(GL_POINT_SIZE,&size);
    return size;
  }
  static GLfloat old;
};

class GLPolygonMode {
public:
  static Vector<2,GLint> Set(GLenum face, GLenum mode) {
    old=Get();
    glPolygonMode(face,mode);
    return old;
  }
  static Vector<2,GLint> SetFill() {
    return Set(GL_FRONT_AND_BACK,GL_FILL);
  }
  static Vector<2,GLint> SetLine() {
    return Set(GL_FRONT_AND_BACK,GL_LINE);
  }
  static Vector<2,GLint> SetPoint() {
    return Set(GL_FRONT_AND_BACK,GL_POINT);
  }
  static void Restore() {
    glPolygonMode(GL_FRONT, old[0]);
    glPolygonMode(GL_BACK,  old[1]);
  }
  static Vector<2,GLint> Get() {
    Vector<2,GLint> v;
    glGetIntegerv(GL_POLYGON_MODE,v.Data());
    return v;
  }
  static Vector<2,GLint> old;
};

class GLClearColor {
public:
  // Parameter must not be reference, otherwise Restore will cause problem.
  // x will be the reference of old, but old will be changed first.
  static Color<GLfloat> Set(const Color<GLfloat> x) {
    old=Get();
    glClearColor(x.r(),x.g(),x.b(),x.a());
    return old;
  }
  static void Restore() {Set(old);}
  static Color<GLfloat> Get() {
    Color<GLfloat> c;
    glGetFloatv(GL_COLOR_CLEAR_VALUE,c.Data());
    return c;
  }
  static Color<GLfloat> old;
};

class GLClearDepth {
public:
  static GLfloat Set(GLfloat x) {
    old=Get();
    glClearDepth(x);
    return old;
  }
  static void Restore() {Set(old);}
  static GLfloat Get() {
    GLfloat c;
    glGetFloatv(GL_DEPTH_CLEAR_VALUE,&c);
    return c;
  }
  static GLfloat old;
};

class GLBindTexture1D {
public:
  static GLint Set(GLuint x) {
    old=Get();
    glBindTexture(GL_TEXTURE_1D, x);
    return old;
  }
  static void Restore() {Set(old);}
  static GLint Get() {
    GLint c;
    glGetIntegerv(GL_TEXTURE_BINDING_1D,&c);
    return c;
  }
  static GLint old;
};

class GLBindTexture2D {
public:
  static GLint Set(GLuint x) {
    old=Get();
    glBindTexture(GL_TEXTURE_2D, x);
    return old;
  }
  static void Restore() {Set(old);}
  static GLint Get() {
    GLint c;
    glGetIntegerv(GL_TEXTURE_BINDING_2D,&c);
    return c;
  }
  static GLint old;
};

class GLBindTexture3D {
public:
  static GLint Set(GLuint x) {
    old=Get();
    glBindTexture(GL_TEXTURE_3D, x);
    return old;
  }
  static void Restore() {Set(old);}
  static GLint Get() {
    GLint c;
    glGetIntegerv(GL_TEXTURE_BINDING_3D,&c);
    return c;
  }
  static GLint old;
};

class GLBindTextureCube {
public:
  static GLint Set(GLuint x) {
    old=Get();
    glBindTexture(GL_TEXTURE_CUBE_MAP, x);
    return old;
  }
  static void Restore() {Set(old);}
  static GLint Get() {
    GLint c;
    glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP,&c);
    return c;
  }
  static GLint old;
};

class GLBindRenderBuffer {
public:
  static GLint Set(GLuint x) {
    old=Get();
    glBindRenderbuffer(GL_RENDERBUFFER, x);
    return old;
  }
  static void Restore() {Set(old);}
  static GLint Get() {
    GLint c;
    glGetIntegerv(GL_RENDERBUFFER_BINDING,&c);
    return c;
  }
  static GLint old;
};

class GLBindFrameBuffer {
public:
  static GLint Set(GLuint x) {
    old=Get();
    glBindFramebuffer(GL_FRAMEBUFFER, x);
    return old;
  }
  static void Restore() {Set(old);}
  static GLint Get() {
    GLint c;
    glGetIntegerv(GL_FRAMEBUFFER_BINDING,&c);
    return c;
  }
  static GLint old;
};

class GLDrawBuffer {
public:
  static GLint Set(GLenum x) {
    old=Get();
    glDrawBuffer(x);
    return old;
  }
  static void Restore() {Set(old);}
  static GLint Get() {
    GLint c;
    glGetIntegerv(GL_DRAW_BUFFER,&c);
    return c;
  }
  static GLint old;
};

class GLReadBuffer {
public:
  static GLint Set(GLenum x) {
    old=Get();
    glReadBuffer(x);
    return old;
  }
  static void Restore() {Set(old);}
  static GLint Get() {
    GLint c;
    glGetIntegerv(GL_READ_BUFFER,&c);
    return c;
  }
  static GLint old;
};

class GLActiveTexture {
public:
  static GLint Set(GLenum x) {
    old=Get();
    glActiveTexture(x);
    return old;
  }
  static void Restore() {Set(old);}
  static GLint Get() {
    GLint c;
    glGetIntegerv(GL_ACTIVE_TEXTURE,&c);
    return c;
  }
  static GLint old;
};

class GLViewport {
public:
  static Vector<4,GLint> Set(GLint x, GLint y, GLint w, GLint h) {
    old=Get();
    glViewport(x,y,w,h);
    return old;
  }
  // Parameter must not be reference, otherwise Restore will cause problem.
  // x will be the reference of old, but old will be changed first.
  static Vector<4,GLint> Set(const Vector<4,GLint> v) {
    return Set(v[0],v[1],v[2],v[3]);
  }
  static void Restore(){Set(old);}
  static Vector<4,GLint> Get() {
    Vector<4,GLint> c;
    glGetIntegerv(GL_VIEWPORT,c.Data());
    return c;
  }
  static Vector<4,GLint> old;
};

class GLColorMask {
public:
  static Vector<4,GLboolean> Set(GLboolean r, GLboolean g, GLboolean b, GLboolean a) {
    old=Get();
    glColorMask(r,g,b,a);
    return old;
  }
  // Parameter must not be reference, otherwise Restore will cause problem.
  // x will be the reference of old, but old will be changed first.
  static Vector<4,GLboolean> Set(const Vector<4,GLboolean> v) {
    return Set(v[0],v[1],v[2],v[3]);
  }
  static void Restore(){Set(old);}
  static Vector<4,GLboolean> Get() {
    Vector<4,GLboolean> c;
    glGetBooleanv(GL_COLOR_WRITEMASK,c.Data());
    return c;
  }
  static Vector<4,GLboolean> old;
};

// Don't use it, until you fully understand how it process
class GLRasterPos {
public:
  static Vector<4,double> Set(double x, double y, double z=0, double w=1) {
    old=Get();
    glRasterPos4d(x, y, z, w);
    Vector<4,double> test=Get();  // this is not the same as the one set
    return old;
  }
  // Parameter must not be reference, otherwise Restore will cause problem.
  // x will be the reference of old, but old will be changed first.
  static Vector<4,double> Set(const Vector<4,double> v) {
    return Set(v[0],v[1],v[2],v[3]);
  }
  static void Restore() {
    Set(old);
  }
  static Vector<4,double> Get() {
    Vector<4,double> c;
    glGetDoublev(GL_CURRENT_RASTER_POSITION,c.Data());
    return c;
  }
  static Vector<4,double> old;
};

class OpenGLProjector {
public:
  OpenGLProjector();
  void Refresh();

  Vector3d UnProject(double winx, double winy, double winz=0.1);
  Vector3d Project(double objx, double objy, double objz);

  GLint viewport_[4];
  GLdouble modelview_[16];
  GLdouble projection_[16];
};


}
